Saltar al contenido principal

7.2.1 Ficheros de Texto

Un fichero de texto puede parecer algo trivial: un archivo con letras guardadas en el disco. Sin embargo, desde el punto de vista del sistema, es un flujo secuencial de datos que debe gestionarse cuidadosamente.

Cuando trabajamos con ficheros no estamos simplemente leyendo o escribiendo texto. Estamos:

  • Solicitando recursos al Sistema Operativo.
  • Gestionando memoria.
  • Controlando punteros internos.
  • Decidiendo cómo y cuándo se escriben realmente los datos en disco.

Entender esto cambia completamente la forma en que se programa.


1️⃣ El Ciclo de Vida del Fichero​

Cada fichero pasa por un ciclo claro:

  1. Se solicita apertura.
  2. El sistema devuelve un descriptor (file descriptor).
  3. Se realizan operaciones (lectura/escritura).
  4. Se libera el recurso mediante cierre.
ojo

Si el recurso no se libera correctamente, el sistema puede quedarse sin descriptores disponibles.

🟩 Apertura de un fichero​

La función open() permite abrir un fichero y devuelve un objeto archivo sobre el que podremos leer o escribir.

Sintaxis general
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

No siempre se usan todos los parámetros, pero los parámetros file y mode son obligatorios.

Cuando usamos:

f = open("notas.txt", "r", encoding="utf-8")

Python:

  • Solicita al Sistema Operativo acceso al archivo.
  • Obtiene un descriptor.
  • Crea un objeto TextIOWrapper.
  • Inicializa un puntero interno en la posición 0.

Ese puntero marca desde dónde se leerá o escribirá.

Si leemos 10 caracteres, el puntero avanza 10 posiciones. La siguiente lectura comienza desde ahí.

Esto explica por qué:

f.read()
f.read() # Devuelve vacío

El puntero ya está al final.

🟦 Codificación: el problema silencioso​

Los archivos no guardan “letras”. Guardan bytes.

La codificación define cómo se traducen esos bytes en caracteres.

Si no especificamos encoding, el comportamiento depende del sistema operativo.

# Forma profesional
with open("datos.txt", "r", encoding="utf-8") as f:
contenido = f.read()

🟨 Modos básicos de apertura​

el modo define cómo se abre el archivo.

Si no se indica, el valor por defecto es 'r' (lectura).

ModoDescripción
'r'Lectura (error si no existe)
'w'Escritura (sobrescribe)
'a'Añadir al final
'x'Crear archivo nuevo
'b'Modo binario
't'Modo texto (por defecto)

Comprender estos modos evita sobrescrituras accidentales.

🟧 Otros parámetros​

En el 95% de los casos reales, usarás:

with open("archivo.txt", "r", encoding="utf-8") as f:
...

Los demás parámetros existen para:

  • Optimizar rendimiento.
  • Controlar codificaciones problemáticas.
  • Gestionar saltos de línea.
  • Trabajar en escenarios avanzados del sistema operativo.

Conocerlos no significa usarlos siempre, sino saber que existen cuando el problema lo requiera.

buffering​

Controla cómo se gestionan los datos en memoria antes de escribirse en disco.

Recordemos que Python utiliza un buffer (memoria temporal) para agrupar operaciones y mejorar el rendimiento.

open("datos.txt", "w", buffering=1)

Valores posibles:

  • 0 → Sin buffer (solo en modo binario).
  • 1 → Buffer por línea (útil para logs).
  • >1 → Tamaño del buffer en bytes.
  • -1 → Automático (valor por defecto).

En la práctica profesional, casi siempre se deja en -1.

errors​

Define qué hacer si ocurre un problema de codificación al leer o escribir.

Por defecto es 'strict', lo que significa que lanzará un error si encuentra un carácter inválido.

open("datos.txt", "r", encoding="utf-8", errors="replace")

Valores habituales:

ValorComportamiento
'strict'Lanza excepción (UnicodeDecodeError)
'ignore'Ignora caracteres problemáticos
'replace'Sustituye caracteres inválidos

Este parámetro es especialmente útil cuando se procesan archivos externos cuya codificación no está garantizada.

newline​

Controla cómo se gestionan los saltos de línea.

Los sistemas operativos usan representaciones distintas:

  • Windows → \r\n
  • Linux / macOS → \n

Por defecto, Python traduce automáticamente estos valores.

En algunos casos (por ejemplo, CSV) es necesario desactivar esta traducción:

open("archivo.csv", "w", newline="", encoding="utf-8")

Si no se especifica correctamente, pueden aparecer líneas en blanco inesperadas.

closefd​

Indica si el descriptor del sistema operativo debe cerrarse cuando se cierra el archivo.

Valor por defecto:

closefd=True

Normalmente no se modifica, salvo en casos avanzados donde se trabaja con descriptores personalizados.

opener​

Permite proporcionar una función personalizada para abrir el archivo.

Es un uso avanzado que permite controlar permisos o comportamiento a bajo nivel.

Ejemplo simplificado:

import os

def mi_opener(path, flags):
return os.open(path, flags, 0o600)

open("privado.txt", "w", opener=mi_opener)

Este parámetro se utiliza en escenarios muy específicos, como aplicaciones que requieren control de permisos a nivel del sistema.


2️⃣ Gestión con with: El estándar profesional​

Cerrar un archivo manualmente es peligroso si ocurre un error antes del close().

❌ Riesgo clásico​

f = open("datos.txt", "w")
f.write("contenido")
# Si aquí ocurre un error...
f.close()

Si el programa falla antes de close(), el archivo queda abierto.

🟩 Context Manager (with)​

with open("datos.txt", "w", encoding="utf-8") as f:
f.write("Datos seguros")

with garantiza:

  • Cierre automático.
  • Liberación del descriptor.
  • Integridad del recurso.
IMPORTANTE

Esto no es una recomendación opcional. Es el estándar profesional.


3️⃣ Estrategias de Lectura​

No todos los archivos deben leerse igual. Depende del tamaño.

🟩 .read() — Cargar todo en memoria​

with open("archivo.txt", "r", encoding="utf-8") as f:
contenido = f.read()

Ventaja:

  • Sencillo.

Desventaja:

  • Consume RAM proporcional al tamaño del archivo.

Adecuado para archivos pequeños.

🟦 .readlines() — Lista de líneas​

with open("archivo.txt", "r", encoding="utf-8") as f:
lineas = f.readlines()

Devuelve una lista donde cada elemento es una línea.

También carga todo en memoria.

🟨 Iteración directa — Método recomendado​

with open("archivo_grande.txt", "r", encoding="utf-8") as f:
for linea in f:
print(linea.strip())

Ventajas:

  • Solo una línea en memoria cada vez.
  • Escalable.
  • Profesional.

Este patrón permite procesar archivos de gigabytes.


4️⃣ El Puntero del Archivo​

El archivo mantiene una posición interna.

🟩 seek() — mover el puntero​

f.seek(0)

Devuelve el puntero al inicio.

🟦 tell() — saber dónde estamos​

posicion = f.tell()

Permite saber la posición actual.

ejemplo
with open("ejemplo.txt", "r", encoding="utf-8") as f:
print(f.read(5))
print(f.tell())

Aquí se observa cómo el puntero avanza.


5️⃣ Escritura y Buffers​

Escribir en un fichero no es solo llamar a write(). En la práctica hay tres ideas clave que suelen generar errores:

  • El modo de apertura determina si se sobrescribe o se añade.
  • write() no añade salto de línea automáticamente.
  • Python usa buffers, así que a veces “parece” que no ha escrito todavía.

🟩 Modos de escritura: w vs a vs x​

Antes de escribir, hay que elegir bien el modo.

ModoQué haceRiesgo típico
'w'Crea o sobrescribe el archivoPierdes el contenido anterior
'a'Crea o añade al finalPuede duplicar información
'x'Crea solo si no existeLanza error si ya existe

Ejemplo:

# Sobrescribe el archivo (borra lo anterior)
with open("log.txt", "w", encoding="utf-8") as f:
f.write("Inicio del programa\n")
# Añade al final
with open("log.txt", "a", encoding="utf-8") as f:
f.write("Nueva línea de log\n")
# Crear sin pisar (útil para no sobrescribir sin querer)
with open("nuevo.txt", "x", encoding="utf-8") as f:
f.write("Archivo creado por primera vez\n")

🟦 write() devuelve cuántos caracteres ha escrito​

Esto es poco conocido, pero útil para depurar:

with open("salida.txt", "w", encoding="utf-8") as f:
n = f.write("Hola")
print(n) # 4

Si n no coincide con lo esperado, es una pista de que el contenido no era el que creías.

🟨 write() NO añade salto de línea automáticamente​

Esto provoca que muchas personas “esperen” líneas separadas y se encuentren todo pegado.

with open("salida.txt", "w", encoding="utf-8") as f:
f.write("Linea 1")
f.write("Linea 2")

Resultado en archivo:

Linea 1Linea 2

Solución:

with open("salida.txt", "w", encoding="utf-8") as f:
f.write("Linea 1\n")
f.write("Linea 2\n")

🟧 writelines(): escribe muchas líneas, pero con trampa​

writelines() escribe una lista de strings, pero no añade \n.

lineas = ["Uno\n", "Dos\n", "Tres\n"]

with open("salida.txt", "w", encoding="utf-8") as f:
f.writelines(lineas)

Si tu lista no incluye \n, quedará todo pegado.

🟥 Buffers: por qué a veces “no se ve” lo escrito​

Cuando escribes en un fichero, Python suele escribir primero en un buffer (memoria temporal) y no en disco inmediatamente.

Esto es normal y se hace por eficiencia.

with open("log.txt", "w", encoding="utf-8") as f:
f.write("Mensaje importante")
# Puede que todavía no se haya escrito físicamente al disco

Normalmente no hay problema, porque al salir del with el archivo se cierra y se vuelca todo.

🟪 flush() para forzar escritura inmediata​

Si estás escribiendo un log o un archivo crítico y quieres asegurarte de que se ha guardado ya:

with open("log.txt", "a", encoding="utf-8") as f:
f.write("Checkpoint\n")
f.flush() # Fuerza el volcado al disco

Esto es útil si el programa puede cerrarse de golpe (por ejemplo, por un error o una caída).

🟫 Escritura segura: evitar corrupción con archivos temporales​

Cuando un archivo es importante, sobrescribirlo directamente con 'w' puede ser peligroso: si el programa se interrumpe a mitad, el fichero queda incompleto.

Estrategia profesional:

  1. Escribir en un temporal
  2. Si todo va bien, renombrar
import os

contenido = "Datos nuevos..."

with open("datos.tmp", "w", encoding="utf-8") as f:
f.write(contenido)

os.replace("datos.tmp", "datos.txt") # Sustitución segura

Esto evita perder el archivo original si algo falla a mitad.


6️⃣ Modos avanzados​

ModoDescripción
'r+'Leer y escribir
'w+'Sobrescribe y permite leer
'a+'Añadir y permitir lectura

🟦 Atención al puntero​

Si escribes y luego quieres leer:

f.write("Hola")
f.seek(0)
print(f.read())

Sin seek(0), no leerás nada.


✅ Buenas prácticas con ficheros de texto​

buenas prácticas
  • Usar siempre with.
  • Especificar encoding.
  • No cargar archivos grandes completos.
  • Manejar errores (FileNotFoundError, PermissionError).
  • No usar rutas absolutas.
  • Pensar en la memoria.

🧪 Ejercicios prácticos – Recuperación del Sistema Central​

El sistema principal ha sufrido una interrupción inesperada. Como técnico del equipo, debes analizar los archivos disponibles y restaurar el sistema a un estado estable.

Dispones de los siguientes ficheros: 👉 Descárgalos aquí

  • entrada.txt → Informe inicial del sistema
  • log.txt → Registro de eventos
  • origen.txt → Configuración del módulo A
  • a.txt → Datos del módulo A
  • b.txt → Datos del módulo B

Cada ejercicio representa una fase del proceso de recuperación.

IMPORTANTE

En todos los ejercicios es obligatorio capturar la excepción FileNotFoundError para gestionar correctamente los casos en los que el archivo no esté disponible, mostrando un mensaje descriptivo por consola.

🟢 Fase 1 – Diagnóstico del sistema​

🟩 Ejercicio 1 – Análisis del informe inicial (entrada.txt)​

Lee completamente entrada.txt (capturando la excepción si el archivo no existe) y muestra:

  • Número total de líneas.
  • Número total de caracteres.
  • La primera línea del informe.
  • La última línea del informe.

Objetivo: practicar lectura completa y manejo básico del archivo.

🟦 Ejercicio 2 – Revisión de errores en el sistema (log.txt)​

Recorre log.txt línea a línea (controlando si el archivo existe) y:

  • Muestra únicamente las líneas que contengan ERROR.
  • Cuenta cuántos errores aparecen en total.

Objetivo: practicar lectura eficiente y búsqueda de patrones.

🟡 Fase 2 – Control del puntero y verificación​

🟨 Ejercicio 3 – Inspección parcial del informe (entrada.txt)​

  1. Lee los primeros 25 caracteres (capturando la excepción si no existe).
  2. Muestra la posición actual del puntero con tell().
  3. Vuelve al inicio con seek(0).
  4. Lee de nuevo los primeros 10 caracteres.

Explica qué ha ocurrido con el puntero en cada paso.

Objetivo: comprender el funcionamiento interno del cursor del archivo.

🔵 Fase 3 – Reconstrucción de módulos​

🟧 Ejercicio 4 – Unificación de módulos (a.txt y b.txt)​

Crea un nuevo archivo llamado sistema_unificado.txt con el siguiente formato:

--- MODULO A ---
(contenido de a.txt)

--- MODULO B ---
(contenido de b.txt)

Debes respetar los saltos de línea y no cargar los archivos completos con .read(). Asegúrate de gestionar la posible ausencia de los archivos de origen.

Objetivo: practicar lectura y escritura controlada.

🟥 Ejercicio 5 – Generación de copia de seguridad (origen.txt)​

Crea un archivo backup_config.txt que contenga exactamente el mismo contenido que origen.txt.

Condición: debe hacerse recorriendo el archivo línea a línea y controlando errores de apertura.

Objetivo: consolidar el patrón correcto de copia de archivos.

🔴 Fase 4 – Actualización segura del sistema​

El sistema ha sido revisado y debe marcarse como operativo.

🟫 Ejercicio 6 – Actualización controlada (entrada.txt)​

  1. Lee el contenido de entrada.txt gestionando la excepción si no existe.
  2. Añade al final la línea:
Estado actual: OPERATIVO
  1. Guarda el nuevo contenido en un archivo temporal entrada.tmp.
  2. Sustituye el archivo original por el temporal usando os.replace().

Objetivo: practicar escritura segura sin riesgo de corrupción.

🟣 Fase Final – Verificación​

🟪 Ejercicio 7 – Confirmación del estado final​

Abre el nuevo entrada.txt (capturando errores si no se encuentra) y comprueba que:

  • Contiene la línea Estado actual: OPERATIVO.
  • No se ha perdido el contenido anterior.